﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EasyCLRBase;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using EasyCLRBase.AssemblyExplorer;
using System.Globalization;

namespace EasyCLRMisc
{
    /// <summary>
    /// Concatinate strings
    /// Original source code: 
    /// <see cref="http://www.mssqltips.com/sqlservertip/2022/concat-aggregates-sql-server-clr-function/"/>
    /// <seealso cref="http://msdn.microsoft.com/en-us/library/ms131056.aspx"/>
    /// </summary>
    [SqlUserDefinedAggregate(
        Format.UserDefined, /// Binary Serialization because of StringBuilder
        IsNullIfEmpty = true,
        Name = "ext_concat",
        MaxByteSize = Constants.AggregateMaxByte)]
    [Serializable]
    [Export(typeof(IClrBase))]
    public struct ConcatenateString : IClrFailoverBase, IBinarySerialize
    {
        private StringBuilder _accumulator;
        private String _delimiter;

        /// <summary>
        /// IsNull property
        /// </summary>
        public Boolean IsNull { get; private set; }

        public void Init()
        {
            this._accumulator = new StringBuilder();
            this._delimiter = string.Empty;
            this.IsNull = true;
        }

        /// <summary>
        /// Create accumulation over format and culture info.
        /// </summary>
        /// <param name="Value">Value to accumulate</param>
        /// <param name="Delimiter">Aggragation separator</param>
        /// <param name="Format">Format String, only the inner format type "{0:\format_string\}"</param>
        /// <param name="Culture">Culture info for formatting Type, like "en-GB", "de-AT"...</param>
        public void Accumulate(
            [Microsoft.SqlServer.Server.SqlFacet(MaxSize = -1)]
            Object Value,
            SqlString Delimiter,
            SqlString Format,
            SqlString Culture)
        {
            Object val = (Value as INullable).GetVariantValue();
            if (val != null)
            {
                this.IsNull = false;
                if (Delimiter.IsNull == false && Delimiter.Value.Length > 0)
                {
                    this._delimiter = Delimiter.Value; /// save for Merge
                    if (this._accumulator.Length > 0)
                        this._accumulator.Append(Delimiter.Value);
                }
                var diff = Convert.ToInt32(Format.IsNull) * 2  + Convert.ToInt32(Culture.IsNull) * 4;
                switch (diff)
                {
                    case 0:
                        val = String.Format(new CultureInfo(Culture.Value), String.Concat("{0:", Format.Value, "}"), val);
                        break;
                    case 2:
                        val = String.Format(new CultureInfo(Culture.Value), "{0}", val);
                        break;
                    case 4:
                        val = String.Format(String.Concat("{0:", Format.Value, "}"), val);
                        break;
                }
                this._accumulator.Append(val);
            }
        }
        /// <summary>
        /// Merge onto the end 
        /// </summary>
        /// <param name="Group"></param>
        public void Merge(ConcatenateString Group)
        {
            /// add the delimiter between strings
            if (this._accumulator.Length > 0 && Group._accumulator.Length > 0)
                this._accumulator.Append(this._delimiter);

            this._accumulator.Append(Group._accumulator.ToString());
        }

        public SqlString Terminate()
        {
            // Put your code here
            return new SqlString(this._accumulator.ToString());
        }

        /// <summary>
        /// deserialize from the reader to recreate the struct
        /// </summary>
        /// <param name="r">BinaryReader</param>
        public void Read(System.IO.BinaryReader r)
        {
            this._delimiter = r.ReadString();
            this._accumulator = new StringBuilder(r.ReadString());

            if (_accumulator.Length != 0)
                this.IsNull = false;
        }

        /// <summary>
        /// searialize the struct.
        /// </summary>
        /// <param name="w">BinaryWriter</param>
        public void Write(System.IO.BinaryWriter w)
        {
            w.Write(this._delimiter);
            w.Write(this._accumulator.ToString());
        }
        #region interface
        public String HowTo()
        {
            throw new NotImplementedException();
        }
        public String Name()
        {
            return "ext_concat";
        }
        public ClrExportString GetClrCreationCode()
        {
            return ClrBase.GetClrFailoverBaseCode((IClrFailoverBase)this);
        }
        #endregion
    }
}
